﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace WorldOnFire
{
    public abstract class ABCObject : ABCClient
    {
        #region Managers

        protected static CRandom s_Ron = new CRandom();

        protected static CObjectManager s_ObjectManager;

        public static void SetObjectManager(CObjectManager _ObjectManager)
        { s_ObjectManager = _ObjectManager; }

        protected static CRenderingManager s_RenderingManager;

        public static void SetRenderingManager(CRenderingManager _RenderingManager)
        { s_RenderingManager = _RenderingManager; }

        protected static ContentManager s_ContentManager;

        public static void SetContentManager(ContentManager _ContentManager)
        { s_ContentManager = _ContentManager; }

        #endregion Managers

        #region Object IDs

        // An enumeration of all abstract classes
        public enum eBaseType
        {
            TURRET          = 0x1,
            MOVING_OBJECT   = 0x2,
            ENEMY           = 0x4,
            PROJECTILE      = 0x8,
        };

        private int m_nBaseTypeID;

        public void SetBaseTypeID(eBaseType[] _eBaseTypes)
        {
            // Reset the ID
            m_nBaseTypeID = 0;

            // Loop through each base type
            foreach( eBaseType _eBaseType in _eBaseTypes )
            {
                // Add each to the base type
                m_nBaseTypeID |= (int)_eBaseType;
            }
        }

        public bool IsBaseType(eBaseType _eBaseType)
        { return (m_nBaseTypeID & (int)_eBaseType) > 0; }

        // TODO: ASSIGN ALL OBJECT IDS. JESUS.
        // TODO: AND THE TEAMS. FOR FUCKS SAKE.

        // An enumeration of all instantiatable classes
        public enum eObjectType
        {
            HOMEWORLD,
            SATELLITE,
            TARGET,
            SHIELD,
            ENEMY_WARP_GATE,
            ENEMY_BOMBER,
            ENEMY_DETONATOR,
            ENEMY_GUARDIAN,
            ENEMY_FRIGATE,
            ENEMY_DESTROYER,
            ENEMY_CRUISER,
            ENEMY_BATTLESHIP,
            ENEMY_CARRIER,
            ENEMY_JUGGERNAUT,
            TURRET_REPEATER,
            TURRET_FLAK,
            TURRET_CANNON,
            TURRET_LASER,
            TURRET_EMP,
            TURRET_GRAVITY_WELL,
            TURRET_BLACK_HOLE,
            TURRET_RELAY,
            PROJECTILE_BULLET,
            PROJECTILE_FLAK,
            PROJECTILE_SHELL,
            PROJECTILE_LASER,
            PROJECTILE_EMP,
            PROJECTILE_BLACK_HOLE,
            PROJECTILE_RELAY,
            PROJECTILE_BOMB,
            PROJECTILE_PLASMA,
            PROJECTILE_MISSILE,
            COUNT
        };

        private eObjectType m_eObjectID;

        protected void SetObjectType(eObjectType _eObjectID)
        { m_eObjectID = _eObjectID; }

        public eObjectType GetObjectType()
        { return m_eObjectID; }

        // An enumerations of teams
        public enum eTeam
        {
            NEUTRAL,
            PLAYER,
            ENEMY,
            COUNT
        };

        private eTeam[] m_eTeam;

        protected void SetTeam(int _nIndex, eTeam _eTeam)
        { m_eTeam[_nIndex] = _eTeam; }

        public eTeam GetTeam(int _nIndex)
        { return m_eTeam[_nIndex]; }

        #endregion Object IDs

        #region Rendering Data

        // Vertex declaration for instance transforms
        public static VertexDeclaration s_InstanceVertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0),
            new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 1),
            new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 2),
            new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 3)
        );

        // Instancing vertex buffer
        protected DynamicVertexBuffer m_TransformVertexBuffer;

        // Model, texture, and effect
        protected CRenderingManager.eModelID m_eModelID;
        protected CRenderingManager.eTextureID m_eTextureID;
        protected CRenderingManager.eEffectID m_eShaderID;
        protected int m_nTechniqueID;

        public int GetTechniqueID()
        { return m_nTechniqueID; }

        public void SetTechniqueID(int _nTechniqueID)
        { m_nTechniqueID = _nTechniqueID; }

        // Rendering states
        protected DepthStencilState m_DepthStencilState;
        protected RasterizerState m_RasterizerState;
        protected BlendState m_BlendState;

        // Bone transforms from the model
        protected Matrix[] m_mBoneTransforms;

        // The array of rendering transforms
        protected Matrix[] m_mRenderingTransforms;

        // Tactical overlay shader
        protected bool m_bOverlay;

        public bool HasTacticalOverlay()
        { return m_bOverlay; }

        #endregion Rendering Data

        #region Activation and Deactivation

        // The total number of objects stored
        protected int m_nObjectCount;

        // The number of active objects
        protected int m_nObjectsActive;

        // The next inactive object index
        protected int m_nInactiveObjectIndex;

        // The array of which objects are active
        protected bool[] m_bObjectActive;

        // The index lists
        protected List<int> m_ActiveIndexList;
        private List<int> m_ActivationIndexList;
        private List<int> m_DeactivationIndexList;
        private List<int> m_CreatedIndexList;

        public void CreateObject(int _nIndex)
        {
            m_CreatedIndexList.Add(_nIndex);
        }

        public void ScheduleForActivation(int _nIndex)
        {
            m_ActivationIndexList.Add(_nIndex);
        }

        public void ScheduleForDeactivation(int _nIndex)
        {
            if (m_DeactivationIndexList.Contains(_nIndex) == false)
            {
                m_DeactivationIndexList.Add(_nIndex);
            }
        }

        public void UpdateObjectActivation()
        {
            int nIndex;

            for (int iObject = 0; iObject < m_ActivationIndexList.Count; ++iObject)
            {
                nIndex = m_ActivationIndexList[iObject];

                m_ActiveIndexList.Add(nIndex);
                m_bObjectActive[nIndex] = true;
                ++m_nObjectsActive;
            }

            for (int iObject = 0; iObject < m_DeactivationIndexList.Count; ++iObject)
            {
                nIndex = m_DeactivationIndexList[iObject];

                m_ActiveIndexList.Remove(nIndex);
                m_CreatedIndexList.Remove(nIndex);
                m_bObjectActive[nIndex] = false;
                --m_nObjectsActive;
            }

            m_ActivationIndexList.Clear();
            m_DeactivationIndexList.Clear();
        }

        protected bool FindUnusedIndex()
        {
            // If there are no objects left to use
            if (m_CreatedIndexList.Count == m_nObjectCount)
            {
                // EJECT
                return false;
            }

            // Because of the early out, this shouldn't be an infinite loop
            while (true)
            {
                // If we've found an inactive object to use
                if (m_CreatedIndexList.Contains(m_nInactiveObjectIndex) == false)
                {
                    // Bail out
                    return true;
                }

                // Increment the index
                ++m_nInactiveObjectIndex;

                // If the index has passed the end of the list
                if (m_nInactiveObjectIndex == m_nObjectCount)
                {
                    // Reset the index to the first projectile
                    m_nInactiveObjectIndex = 0;
                }
            }
        }

        #endregion Activation and Deactivation

        #region Object Data

        // Positional and collision data
        protected Matrix[] m_mTransform;
        protected float[] m_fRadius;

        #endregion Object Data

        #region Accessors and Mutators

        public int GetObjectCount()
        { return m_nObjectCount; }

        public List<int> GetActiveIndexList()
        { return m_ActiveIndexList; }

        public int GetActiveObjectCount()
        { return m_ActiveIndexList.Count; }

        public bool IsObjectActive(int _nIndex)
        { return m_bObjectActive[_nIndex]; }

        public Matrix[] GetTransforms()
        { return m_mTransform; }

        public Matrix GetTransform(int _nIndex)
        { return m_mTransform[_nIndex]; }

        public Vector3 GetPosition(int _nIndex)
        { return m_mTransform[_nIndex].Translation; }

        public float GetRadius(int _nIndex)
        { return m_fRadius[_nIndex]; }

        public void SetTransform(int _nIndex, Matrix _mTransform)
        { m_mTransform[_nIndex] = _mTransform; }

        public void SetPosition(int _nIndex, Vector3 _vPosition)
        { m_mTransform[_nIndex].Translation = _vPosition; }

        #endregion Accessors and Mutators

        #region Initialization

        public ABCObject()
        {

        }

        public virtual void Initialize(int _nObjectCount)
        {
            // Set the object count
            m_nObjectCount = _nObjectCount;

            // Initialize the arrays
            m_bObjectActive = new bool[_nObjectCount];
            m_eTeam = new eTeam[_nObjectCount];

            m_mTransform = new Matrix[_nObjectCount];
            m_fRadius = new float[_nObjectCount];

            // Initialize transforms to identity
            for (int iObject = 0; iObject < m_nObjectCount; ++iObject)
            {
                m_mTransform[iObject] = Matrix.Identity;
                m_fRadius[iObject] = 1.0f;
            }

            // Initialize the activation lists
            m_ActiveIndexList = new List<int>();
            m_ActivationIndexList = new List<int>();
            m_DeactivationIndexList = new List<int>();
            m_CreatedIndexList = new List<int>();

            // Initialize the rendering IDs to defaults
            m_eTextureID = CRenderingManager.eTextureID.INVALID;
            m_eModelID = CRenderingManager.eModelID.INVALID;
            m_eShaderID = CRenderingManager.eEffectID.OBJECT;
            m_nTechniqueID = (int)CRenderingManager.eObjectTechnique.INSTANCING;

            // Initialize the rendering states to defaults
            m_DepthStencilState = DepthStencilState.Default;
            m_RasterizerState = RasterizerState.CullCounterClockwise;
            m_BlendState = BlendState.Opaque;
            m_bOverlay = false;

            // Load stuff
            this.LoadContent();
        }

        
        public virtual void LoadContent()
        {
            // If a model has been loaded
            if (m_eModelID != CRenderingManager.eModelID.INVALID)
            {
                // Retrieve the model from the manager
                Model ObjectModel = s_RenderingManager.GetModel(m_eModelID);

                // Copy the bone transforms for easy access
                m_mBoneTransforms = new Matrix[ObjectModel.Bones.Count];
                ObjectModel.CopyAbsoluteBoneTransformsTo(m_mBoneTransforms);
            }

            // TODO: FUCK THIS. MAKE TEXTURES, FATTY
            if (m_eTextureID == CRenderingManager.eTextureID.INVALID)
            {
                m_eTextureID = CRenderingManager.eTextureID.ENEMY_ILLUMINATION;
            }
        }

        public virtual void Shutdown()
        {

        }

        #endregion Initialization

        #region Update and Render

        public virtual void Update(float _fDeltaTime)
        {

        }

        public virtual void Render(GraphicsDevice _GraphicsDevice)
        {
            // If there are no active objects or there is no model
            if (m_nObjectsActive == 0 || m_eModelID == CRenderingManager.eModelID.INVALID)
            {
                // Eject
                return;
            }

            // Set render states
            _GraphicsDevice.DepthStencilState = m_DepthStencilState;
            _GraphicsDevice.RasterizerState = m_RasterizerState;
            _GraphicsDevice.BlendState = m_BlendState;

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Set up rendering transforms and buffers ====================================*/
            // Create the array of rendering transforms
            m_mRenderingTransforms = new Matrix[m_nObjectsActive];

            // Copy the transforms
            this.CopyRenderingTransforms();
            
            // If the vertex buffer is uninitialized or too small
            if ((m_TransformVertexBuffer == null) ||
                (m_TransformVertexBuffer.VertexCount < m_mRenderingTransforms.Length))
            {
                // If the vertex buffer is initialized
                if (m_TransformVertexBuffer != null)
                {
                    // Delete it
                    m_TransformVertexBuffer.Dispose();
                }

                // Allocate a new vertex buffer
                m_TransformVertexBuffer = 
                    new DynamicVertexBuffer(_GraphicsDevice, s_InstanceVertexDeclaration, 
                        m_mRenderingTransforms.Length, BufferUsage.WriteOnly);
            }

            // Transfer the instance transforms to the buffer
            m_TransformVertexBuffer.SetData(m_mRenderingTransforms, 0, 
                m_mRenderingTransforms.Length, SetDataOptions.Discard);
            /* END: Set up rendering transforms and buffers ======================================*/
            ////////////////////////////////////////////////////////////////////////////////////////

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Render stuff ===============================================================*/
            // Get the effect from the rendering manager
            Effect ObjectEffect = s_RenderingManager.GetEffect(m_eShaderID);
            ObjectEffect.CurrentTechnique = ObjectEffect.Techniques[m_nTechniqueID];
            ObjectEffect.Parameters["g_Texture"].SetValue(s_RenderingManager.GetTexture(m_eTextureID));

            // Get the model from the rendering manager
            Model ObjectModel = s_RenderingManager.GetModel(m_eModelID);

            _GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            // Loop through each mesh
            foreach (ModelMesh Mesh in ObjectModel.Meshes)
            {
                // Set the world matrix
                ObjectEffect.Parameters["g_mWorld"].SetValue(m_mBoneTransforms[Mesh.ParentBone.Index]);

                // Loop through each mesh part
                foreach (ModelMeshPart MeshPart in Mesh.MeshParts)
                {
                    // Set the GPU to read from both buffers (mesh part vertices and instance transforms)
                    _GraphicsDevice.SetVertexBuffers(
                        new VertexBufferBinding(MeshPart.VertexBuffer, MeshPart.VertexOffset, 0),
                        new VertexBufferBinding(m_TransformVertexBuffer, 0, 1));

                    // Set the index buffer
                    _GraphicsDevice.Indices = MeshPart.IndexBuffer;

                    // Loop through each pass
                    foreach (EffectPass Pass in ObjectEffect.CurrentTechnique.Passes)
                    {
                        // Begin the pass
                        Pass.Apply();

                        // Draw magic things
                        _GraphicsDevice.DrawInstancedPrimitives(
                            PrimitiveType.TriangleList, 0, 0,
                            MeshPart.NumVertices, MeshPart.StartIndex,
                            MeshPart.PrimitiveCount, m_mRenderingTransforms.Length);
                    }
                }
            }
            /* END: Render stuff =================================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
        }

        protected virtual void CopyRenderingTransforms()
        {
            // Loop through each index for rendering
            for (int iRenderingIndex = 0; iRenderingIndex < m_nObjectsActive; ++iRenderingIndex)
            {
                // Copy the transform into the rendering transform array
                m_mRenderingTransforms[iRenderingIndex] =
                    m_mTransform[m_ActiveIndexList[iRenderingIndex]];
            }
        }

        public virtual bool RespondToCollision(int _nIndex, ABCObject _Object, int _nObjectIndex)
        {
            // Leave this to the derived classes
            return false;
        }

        #endregion Update and Render

        #region Helper Functions

        public int GetFirstCollidingIndex(Vector3 _vPosition, float _fRadius)
        {
            for (int iObject = 0; iObject < m_nObjectCount; ++iObject)
            {
                if (m_bObjectActive[iObject] == false)
                {
                    continue;
                }

                // Calculate the combined radius
                float fCombinedRadius = m_fRadius[iObject] + _fRadius;

                // Calculate the vector between the objects
                Vector3 vBetween = this.GetPosition(iObject) - _vPosition;

                // If the distance between the objects is less than their combined radii
                if (vBetween.LengthSquared() < fCombinedRadius * fCombinedRadius)
                {
                    return iObject;
                }
            }

            return -1;
        }

        public List<int> GetCollidingIndexList(Vector3 _vPosition, float _fRadius)
        {
            List<int> IndexList = new List<int>();

            for (int iObject = 0; iObject < m_nObjectCount; ++iObject)
            {
                if (m_bObjectActive[iObject] == false)
                {
                    continue;
                }

                // Calculate the combined radius
                float fCombinedRadius = m_fRadius[iObject] + _fRadius;

                // Calculate the vector between the objects
                Vector3 vBetween = this.GetPosition(iObject) - _vPosition;

                // If the distance between the objects is less than their combined radii
                if (vBetween.LengthSquared() < fCombinedRadius * fCombinedRadius)
                {
                    IndexList.Add(iObject);
                }
            }

            return IndexList;
        }

        public bool IsColliding(int _nIndex, Vector3 _vPosition, float _fRadius, bool _bUseObjectRadius)
        {

            // Calculate the combined radius
            float fCombinedRadius = _fRadius + (_bUseObjectRadius ? m_fRadius[_nIndex] : 0.0f);

            // Find the vector between the two
            Vector3 vBetween = this.GetPosition(_nIndex) - _vPosition;

            // If the distance between the objects is less than their combined radii
            if (vBetween.LengthSquared() < fCombinedRadius * fCombinedRadius)
            {
                return true;
            }

            return false;
        }

        protected void TurnTowards(int _nIndex, Vector3 _vPosition, float _fRotationSpeed, bool _bCorrectAxes)
        {
            // Calculate the vector between the object and position
            Vector3 vBetween = _vPosition - m_mTransform[_nIndex].Translation;

            // Calculate the dot products for the X and Y axes
            float fDotX = Vector3.Dot(vBetween, m_mTransform[_nIndex].Right);
            float fDotY = Vector3.Dot(vBetween, m_mTransform[_nIndex].Up);

            Vector2 vRotationSpeeds = new Vector2();

            // If the target is to the right
            if (fDotX > 0.0f)
            {
                // Set the rotation speed to maximum by default
                vRotationSpeeds.Y = _fRotationSpeed;
            }
            // If the target is to the left
            else if (fDotX < 0.0f)
            {
                // Set the rotation speed to maximum by default
                vRotationSpeeds.Y = -_fRotationSpeed;
            }

            // If the target is above
            if (fDotY > 0.0f)
            {
                // Set the rotation speed to maximum by default
                vRotationSpeeds.X = _fRotationSpeed;
            }
            // If the target is below
            else if (fDotY < 0.0f)
            {
                // Set the rotation speed to maximum by default
                vRotationSpeeds.X = -_fRotationSpeed;
            }

            // If the target is behind
            if(Vector3.Dot(vBetween, m_mTransform[_nIndex].Forward) < 0.0f)
            {
                // Invert the rotation speed on the X axis
                vRotationSpeeds.X = -vRotationSpeeds.X;
            }

            // If there is no rotation
            if (vRotationSpeeds == Vector2.Zero)
            {
                // EJECT
                return;
            }

            // Make sure the rotation doesn't exceed the rotation speed
            vRotationSpeeds = _fRotationSpeed * Vector2.Normalize(vRotationSpeeds);

            // Determine the rotation matrix from the rotation speeds
            Matrix mRotation = Matrix.CreateFromYawPitchRoll(vRotationSpeeds.Y, vRotationSpeeds.X, 0.0f);

            // Save and remove the translation from the transform
            Vector3 vPosition = m_mTransform[_nIndex].Translation;
            m_mTransform[_nIndex].Translation = Vector3.Zero;

            // Rotate by the given amount
            m_mTransform[_nIndex] = mRotation * m_mTransform[_nIndex];

            // Correct the axes
            if (_bCorrectAxes == true)
            {
                //m_mTransform[_nIndex].Backward = Vector3.Normalize(m_mTransform[_nIndex].Backward);
                m_mTransform[_nIndex].Right = Vector3.Normalize(Vector3.Cross(Vector3.Up, m_mTransform[_nIndex].Backward));
                m_mTransform[_nIndex].Up = Vector3.Normalize(Vector3.Cross(m_mTransform[_nIndex].Backward, m_mTransform[_nIndex].Right));
            }

            // Add the translation back
            m_mTransform[_nIndex].Translation = vPosition;
        }

        #endregion Helper Functions
    }
}
